package org.fjsei.yewu.bpm;

import io.camunda.spring.client.exception.BpmnError;
import io.camunda.zeebe.client.api.response.ActivatedJob;
import io.camunda.zeebe.client.api.worker.JobClient;
import io.camunda.zeebe.spring.client.annotation.JobWorker;
import io.camunda.zeebe.spring.client.annotation.Variable;
import io.camunda.zeebe.spring.client.annotation.VariablesAsType;
//import io.camunda.spring.client.properties.CamundaClientProperties $ClientMode
//import io.camunda.zeebe.spring.client.exception.ZeebeBpmnError;
//import io.camunda.zeebe.spring.common.exception.ZeebeBpmnError;
import lombok.extern.slf4j.Slf4j;
import md.cm.flow.ApprovalStm;
import md.specialEqp.inspect.Agreement;
import md.specialEqp.inspect.Agreements;
import md.specialEqp.inspect.ProtocolSta_Enum;
import md.system.User;
import md.system.UserRepository;
import org.fjsei.yewu.resolver.Comngrql;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static graphql.Assert.assertNotNull;
import static graphql.Assert.assertTrue;

/**协议 申请单的；
 * BPMN文件上面的节点若没有相应的客户机代码来处理也不报错的。
 * worker:针对特定流程，版本有别根据ID来的，分布式服务必须最少有一个worker接入来处理。UserTask是特殊的Job worker;
 * 整个Zeebe流程搞下来：就像拼凑的代码片段，更难于驾驭。
* */
@Slf4j
//@ConditionalOnProperty(name = "sei.zeebe", havingValue = "true")
@Component
public class AskForApprovalWorker extends Comngrql {
//    @Autowired
//    private UniversalService universalService;
    private final Agreements agreements;
    private final UserRepository userRepository;

    public AskForApprovalWorker(Agreements agreements, UserRepository userRepository) {
        this.agreements = agreements;
        this.userRepository = userRepository;
    }

    //必须是报告流转的 FlowStat?
    private ApprovalStm setApprovalStmStatus(AskForApprProcessVariables variables, ProtocolSta_Enum status){
        Agreement entFlow = entityOf(variables.getEnt(), Agreement.class);   //或者其他的申请单实体 都 instance of FlowStat
        if(null==entFlow)   throw new BpmnError("notFind", "没找到流转实体");
//        ApprovalStm stm= entFlow.getStm();	  //仅仅是状态和历史操作记录，不是真正的流程引擎服务。
//        if(null==stm)   throw new BpmnError("notFind", "没状态机");
        entFlow.setStatus(status);        //当前位于进入审核环节
        agreements.save(entFlow);
        return null;
    }
    private ApprovalStm setApprovalStmStatus(String entId,ProtocolSta_Enum status){
        Agreement entFlow = entityOf(entId, Agreement.class);   //或者其他的申请单实体 都 instance of FlowStat
        if(null==entFlow)   throw new BpmnError("notFind", "没找到流转实体");
        entFlow.setStatus(status);        //当前位于进入审核环节
        agreements.save(entFlow);
        return null;
    }
    //手动提交@JobWorker版本; 支持修改流程变量。
    @Deprecated
//    @JobWorker(type="agreement_status_show", autoComplete=false)
    public void onAgreementStatusShow_删除(final JobClient client, final ActivatedJob job, @VariablesAsType AskForApprProcessVariables variables){
        //ApprovalStm stm=setApprovalStmStatus(variables,Procedure_Enum.CHECK);        //当前位于进入审核环节
        log.info("协议 申请单, 结果：{}", variables);     //【奇怪】为何这个节点等待5分多钟时间啊，毛病
        variables.setPromoter("herzhang");
        client.newCompleteCommand(job.getKey()).variables(variables).send()
                .exceptionally( throwable -> { throw new RuntimeException("Could not complete job " + job, throwable); });
    }
    /**内部代码# 必须同步完成的，不能异步方式直接返回！
     * 只能针对协议 申请单这个BPMN注入的代码片段
     * Service Task：在BPMN配置FEEL计算方式太不灵活了！还是改成Java代码来解决。BPMN引擎只需要配置输入输出的Mapping就可以。这里代码variables.setXX直接修改后配合输出。
     * 窗口人员最少1个人，最多人数不限制，可以任意一个人接受就自动撤销其它同行人员的本节点的任务激活和显示。
     * */
    //@ConditionalOnProperty(name = "sei.zeebe", havingValue = "true")
    @JobWorker(type = "agreement_set_promoter")
    public Object  handleSetPromoter(final JobClient client, final ActivatedJob job,@Variable List<String>  ispMens, @Variable List<String> allsigned,
                              @Variable String ent) {
        Map<String, Object>  variablem=new HashMap<>();
        //ApprovalStm stm = setApprovalStmStatus(ent, Procedure_Enum.CHECK);        //当前位于进入审核环节
        log.info("协议 申请单, 结果：{}", ent);     //【奇怪】为何这个节点等待5分多钟时间啊，毛病
        //不一定都得是Report实体类。 申请单都行;
        //            universalService.goUri(variables.getUri());
        int maxmans = allsigned.size();
        assertTrue(maxmans > 0 && maxmans == ispMens.size(), () -> "窗口人数不对");
        int i;
        for (i = 0; i < maxmans; i++)
            if ("Y".equals(allsigned.get(i)))     break;
        //【人性化考虑】多人一起走的：允许踢皮球，要不然只能让UserTask一致浮现在没接单人的视野中=太多就杂乱了，不想接受允许窗口人直接拒绝接单，都否绝的只能流程图上退回发起人视野了。
        if (i < maxmans) {
            variablem.put("promoter", ispMens.get(i));
            User promoter=userRepository.findByUsername(ispMens.get(i));
            assertNotNull(promoter, () -> "zeebe用户在不在本平台中");
            Agreement entFlow = entityOf(ent, Agreement.class);
            if(null==entFlow)   throw new BpmnError("notFind", "没找到流转实体");
            entFlow.setPromoter(promoter);
            agreements.save(entFlow);
        }
        else variablem.put("promoter", null);      //没人愿意干活啊
        //            variables.getAllsigned().forEach();
        //            variables.getIspMens();
        return variablem;
    }
    @Deprecated
    //@ConditionalOnProperty(name = "sei.zeebe", havingValue = "true")
    @JobWorker(type="agreement_set_promoter22", autoComplete=true)
    public Object onAgreementStatusShow删除(final JobClient client, final ActivatedJob job, @VariablesAsType AskForApprProcessVariables variables) {
        //ApprovalStm stm = setApprovalStmStatus(variables, Procedure_Enum.CHECK);        //当前位于进入审核环节
        log.info("协议 申请单, 结果：{}", variables);     //【奇怪】为何这个节点等待5分多钟时间啊，毛病
//        int maxmans = variables.getAllsigned().size();
//        assertTrue(maxmans > 0 && maxmans == variables.getIspMens().size(), () -> "窗口人数不对");
//        int i;
//        for (i = 0; i < maxmans; i++)
//            if ("true".equals(variables.getAllsigned().get(i)))     break;
//        //【人性化考虑】多人一起走的：允许踢皮球，要不然只能让UserTask一致浮现在没接单人的视野中=太多就杂乱了，不想接受允许窗口人直接拒绝接单，都否绝的只能流程图上退回发起人视野了。
//        if (i < maxmans)  variables.setReviewer(variables.getIspMens().get(i));
//        else variables.setReviewer(null);        //没人愿意干活啊
        return variables;
    }

//    @JobWorker(type = "foo")
//    public void handleJobFoo(final JobClient client, final ActivatedJob job) {
//        client.newCompleteCommand(job.getKey())
//                .send()
//                .exceptionally( throwable -> { throw new RuntimeException("Could not complete job " + job, throwable); });
//           }


/*       #Return a Map, String, InputStream, or Object, which then will be added to the process variables
    @JobWorker(type = "foo")
    public Map<String, Object> handleJobFoo(final ActivatedJob job) {
      if (successful) {
             // some data is returned to be stored as process variable
        return variablesMap;
      } else {
       throw new BpmnError("DOESNT_WORK", "This does not work because...");
      }
    }
    @JobWorker
    public ProcessVariables foo(@VariablesAsType ProcessVariables variables, @CustomHeaders Map<String, String> headers){
      return variables;
    }
* */

    //真的没有代码来针对节点type=“”处理的， zeebe走不动也不报警的，被滞留无法终结的流程实例。
    @JobWorker(type="agreement_req_cancel", autoComplete=true)
    public void handleReportApprove(@VariablesAsType AskForApprProcessVariables variables) throws Exception {
        //ApprovalStm stm=setApprovalStmStatus(variables,Procedure_Enum.APPR);
//        try {
        ApprovalStm stm=setApprovalStmStatus(variables,ProtocolSta_Enum.CANCEL);
        log.info("申请单撤销中！ent：{}", variables.getEnt());
//            universalService.goUri(variables.getUri());
//        } catch (DuplicateReportException ex) {
//            throw new BpmnError("duplicateMessage", "Could not post report, it is a duplicate.");
//        }
    }

    @JobWorker(type="agreement_status_calcfee", autoComplete=true)
    public void handleApprove1(@VariablesAsType AskForApprProcessVariables variables) throws Exception {
//        ApprovalStm stm=setApprovalStmStatus(variables,Procedure_Enum.APPR);
        log.info("去任务和敲定收费！ent={}", variables.getEnt());
    }
    @JobWorker(type="agreement_status_feefinish", autoComplete=true)
    public void handleApprove2(@VariablesAsType AskForApprProcessVariables variables) throws Exception {
//        ApprovalStm stm=setApprovalStmStatus(variables,Procedure_Enum.APPR);
        log.info("收费明确！ent={}", variables.getEnt());
    }
    /**【特别小心！】 @Transactional 必须加：zeebe流程重试也是org.springframework.transaction.TransactionSystemException: Could not commit JPA transaction;
     * 似乎zeebe自己在BPMN定义图形当中搞了个类似的Transactional的标签？？ @两者有关系吗？？
     *【局限】注入参数不能同时出现@VariablesAsType AskForApprProcessVariables ,@Variable String 混合后@VariablesAsType就没数据！
     * @Variable String ent
     * 注意Worker不同用的流程bpmn文件拷贝type="agreement_entity_status_set"，同名字代码执行随机跳转的，不一定这个代码内执行。所以要确保type在流程引擎zeebe集群范围的唯一性定义避免争夺和差错。
     * zeebe流程图上面Input Mapping实际在该节点运行前准备数据好的字段。
     * @Transactional 必须加： 就算.save()也不能确保真的爆出到数据库。
     * 【特别注意】这个代码涉及数据库事务，很可能需要重新尝试执行的(而CRDB数据库更需要有重试机会)，因此不一定能够立刻都能见到成功运行结束的。
     * 悲观锁失败？Worker rearend failed to handle job with key  of type , sending fail command to broker org.springframework.orm.ObjectOptimisticLockingFailureException:手动改数据遇见？
     * @JobWorker 是否必须放在@Transactional后面的：CRDB事务很可能重试，导致问题大：@Transactional里面掺杂流程触发代码，导致流程引擎实际上早就执行了，可是数据库却还会重试执行，流程代码重复执行啊,流程图已经往前走了吧？！
     * 【老执行不成功】retry times老不够用；实际和infinispan抛出Error没有关联的；估计是因为电脑负荷太大，延迟太大了，性能差导致gprc的时间控制机制问题，所以被zeebe当成失败了。导致Operate遗留Incident不正常要手动删除ES索引。
     * 为何数据库执行要和流程函数要独立开去！ 数据库可以回退，zeebe流程接口发送出去就不能退回了。【很大毛病】俩个代码逻辑要独立分离才对！注解顺序@Transactional@JobWorker顺序才对：不能对调！！
     * BPMN流程图上服务节点报错的若不是当前活动节点的就将无法被retry，会一直保留红色报错标记，往回重走流程也不会自动清除红色标记的。不是当前活跃节点retry没效果的。
     * */
    @Transactional
    @JobWorker(type="agreement_status_approve")
    public void handleApprove3(@VariablesAsType AskForApprProcessVariables variables) throws Exception {
        ApprovalStm stm=setApprovalStmStatus(variables,ProtocolSta_Enum.SIGNING);
        log.info("协议审批好！ent={}", variables.getEnt());
    }

    @JobWorker(type="agreement_sign_finish")
    public void handleApprove4(@VariablesAsType AskForApprProcessVariables variables) throws Exception {
        ApprovalStm stm=setApprovalStmStatus(variables,ProtocolSta_Enum.SIGNED);
        log.info("协议签订完！ent={}", variables.getEnt());
    }
    @JobWorker(type="agreement_sign_delete", autoComplete=true)
    public void handleApprove5(@VariablesAsType AskForApprProcessVariables variables) throws Exception {
        ApprovalStm stm=setApprovalStmStatus(variables,ProtocolSta_Enum.CANCEL);
        log.info("协议最后作废！ent={}", variables.getEnt());
    }
    @JobWorker(type="agreement_status_fallback", autoComplete=true)
    public void handleApprove6(@VariablesAsType AskForApprProcessVariables variables) throws Exception {
        ApprovalStm stm=setApprovalStmStatus(variables,ProtocolSta_Enum.SUBMIT);
        log.info("审批时协议后退客户！ent={}", variables.getEnt());
    }
    @JobWorker(type="agreement_status_denysign")
    public void handleApprove7(@VariablesAsType AskForApprProcessVariables variables) throws Exception {
        //实体模型字段  pk 和zeebe状态可能不能同步， 网页缓冲影响？？
        ApprovalStm stm=setApprovalStmStatus(variables,ProtocolSta_Enum.CHECK);
        log.info("客户不满足！ent={} say={}", variables.getEnt(), variables.getSay());
    }

//    @JobWorker(type = "foo", fetchVariables={"variable1", "variable2"})
//    public void handleJobFoo(final JobClient client, final ActivatedJob job) {
//        String variable1 = (String)job.getVariablesAsMap().get("variable1");
//    }

    /**可以在*.BPMN流程图中多次出现同一个Type的service_TASK的，带输入参数entStatus来区分不同节点的作用。
     *【局限】注入参数不能同时出现@VariablesAsType AskForApprProcessVariables ,@Variable String 混合后@VariablesAsType就没数据！
     * @Variable String ent
     * 注意Worker不同用的流程bpmn文件拷贝type="agreement_entity_status_set"，同名字代码执行是同一个==报错。
     * zeebe流程图上面Input Mapping实际在该节点运行前准备数据好的字段。
     * */
    @JobWorker(type="agreement_entity_status_set", autoComplete=true)
    public void handleEntityStatusSet(@Variable String ent,@Variable String entStatus) throws Exception {
        //实体模型字段  pk 和zeebe状态可能不能同步， 网页缓冲影响？？
        ProtocolSta_Enum stato=ProtocolSta_Enum.SUBMIT;
        if(StringUtils.hasText(entStatus))  stato=ProtocolSta_Enum.valueOf(entStatus);
        ApprovalStm stm=setApprovalStmStatus(ent,stato);
        log.info("更新状态：ent={} status={}",ent,entStatus);
    }
}


//问题：Job worker 工作者代码必须是幂等的。
//直接用zeebe-client.Api太不友好, 没法直接的对UserTask读取当前流程实例  https://github.com/camunda-community-hub/spring-zeebe
